package com.wmada.toolkit.common;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * The type Reflect utils.
 */
@SuppressWarnings({"unused", "unchecked"})
public class ReflectUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtils.class);

    /**
     * Gets .
     *
     * @param <T>       the type parameter
     * @param bean      the bean
     * @param fieldName the field name
     * @return the
     */
    public static <T> T getter(Object bean, String fieldName) {
        T value = null;
        try {
            fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Method method = bean.getClass().getMethod("get" + fieldName);
            value = (T) ConvertUtils.convertType(method.invoke(bean), method.getReturnType());
        } catch (Exception e) {
            LOGGER.debug("获取" + fieldName + "失败！", e);
        }
        return value;
    }

    public static void setter(Object bean, String fieldName, Object value) {
        try {
            fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Method method = bean.getClass().getMethod("set" + fieldName, value.getClass());
            method.invoke(bean, value);
        } catch (Exception e) {
            LOGGER.debug("设置" + fieldName + "失败！", e);
        }
    }

    /**
     * Reflect t.
     *
     * @param <T>       the type parameter
     * @param fields    the fields
     * @param className the class name
     * @return the t
     */
    @SuppressWarnings("unchecked")
    public static <T> T reflect(Map<String, Object> fields, String className) {
        try {
            Class<?> clazz   = Class.forName(className);
            T        object  = (T) clazz.newInstance();
            Method[] methods = clazz.getMethods();
            traverseMethods(fields, object, methods);
            return object;
        } catch (Exception e) {
            LOGGER.debug("#对象创建出错：初始化失败！class:" + className, e);
            return null;
        }
    }

    /**
     * Reflect t.
     *
     * @param fields the fields
     * @param <T>    the target
     * @return the t
     */
    public static <T> T reflect(Map<String, Object> fields, T target) {
        Method[] methods = target.getClass().getMethods();
        traverseMethods(fields, target, methods);
        return target;
    }

    /**
     * Reflect t.
     *
     * @param fields the fields
     * @param clazz  the clazz
     * @return the t
     */
    public static <T> T reflect(Map<String, Object> fields, Class<T> clazz) {
        try {
            T        object  = clazz.newInstance();
            Method[] methods = clazz.getMethods();
            traverseMethods(fields, object, methods);
            return object;
        } catch (Exception e) {
            LOGGER.debug("#对象创建出错：初始化失败！class:" + clazz.getName(), e);
            return null;
        }
    }

    /**
     * Reflect with underline t.
     *
     * @param <T>       the type parameter
     * @param fields    the fields
     * @param className the class name
     * @return the t
     */
    @SuppressWarnings({"unchecked", "WeakerAccess"})
    public static <T> T reflectWithUnderline(Map<String, Object> fields, String className) {
        try {
            Class<?> clazz   = Class.forName(className);
            T        object  = (T) clazz.newInstance();
            Method[] methods = clazz.getMethods();
            traverseMethodsWithUnderLine(fields, object, methods);
            return object;
        } catch (Exception e) {
            LOGGER.debug("#对象创建出错：初始化失败！class:" + className, e);
            return null;
        }
    }

    /**
     * Reflect with underline t.
     *
     * @param <T>    the type parameter
     * @param fields the fields
     * @param target the target
     * @return the t
     */
    public static <T> T reflectWithUnderline(Map<String, Object> fields, T target) {
        Method[] methods = target.getClass().getMethods();
        traverseMethodsWithUnderLine(fields, target, methods);
        return target;
    }

    /**
     * Reflect t.
     *
     * @param fields the fields
     * @param <T>    the target
     * @return the t
     */
    public static <T> T reflectWithParamList(Map<String, String[]> fields, T target) {
        Method[] methods = target.getClass().getMethods();
        traverseListMethods(fields, target, methods);
        return target;
    }

    /**
     * Reflect t.
     *
     * @param fields the fields
     * @param clazz  the clazz
     * @return the t
     */
    public static <T> T reflectWithParamList(Map<String, String[]> fields, Class<T> clazz) {
        try {
            T        object  = clazz.newInstance();
            Method[] methods = clazz.getMethods();
            traverseListMethods(fields, object, methods);
            return object;
        } catch (Exception e) {
            LOGGER.debug("#对象创建出错：初始化失败！class:" + clazz.getName(), e);
            return null;
        }
    }

    /**
     * Reflect with underline t.
     *
     * @param <T>    the type parameter
     * @param fields the fields
     * @param clazz  the clazz
     * @return the t
     */
    public static <T> T reflectWithUnderline(Map<String, Object> fields, Class<T> clazz) {
        try {
            T        object  = clazz.newInstance();
            Method[] methods = clazz.getMethods();
            traverseMethodsWithUnderLine(fields, object, methods);
            return object;
        } catch (Exception e) {
            LOGGER.debug("#对象创建出错：初始化失败！class:" + clazz.getName(), e);
            return null;
        }
    }

    private static <T> void traverseMethodsWithUnderLine(Map<String, Object> fields, T object, Method[] methods) {
        for (Method method : methods) {
            if (method.getDeclaringClass() != Object.class) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    String fieldName = method.getName().replaceFirst("^(set|is)", "");
                    fieldName = NameUtils.toUnderline(fieldName);
                    Object valueStr = fields.get(fieldName);
                    setValue(object, method, parameterTypes, fieldName, valueStr);
                }
            }
        }
    }

    private static <T> void traverseMethods(Map<String, Object> fields, T object, Method[] methods) {
        for (Method method : methods) {
            if (method.getDeclaringClass() != Object.class) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    String fieldName = method.getName().replaceFirst("^(set|is)", "");
                    fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                    Object valueStr = fields.get(fieldName);
                    setValue(object, method, parameterTypes, fieldName, valueStr);
                }
            }
        }
    }

    private static <T> void traverseListMethods(Map<String, String[]> fields, T object, Method[] methods) {
        for (Method method : methods) {
            if (method.getDeclaringClass() != Object.class) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    String fieldName = method.getName().replaceFirst("^(set|is)", "");
                    fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                    String[] valueList = fields.get(fieldName);
                    if (valueList == null || valueList.length == 0) {
                        valueList = fields.get(fieldName + "[]");
                    }
                    setListValue(object, method, parameterTypes, fieldName, valueList);
                }
            }
        }
    }

    private static <T> void setValue(T object, Method method, Class<?>[] parameterTypes, String fieldName, Object valueStr) {
        if (valueStr != null) {
            try {
                Object value = ConvertUtils.convertType(valueStr, parameterTypes[0]);
                if (value != null) {
                    method.invoke(object, value);
                }
            } catch (Exception e) {
                LOGGER.debug("#字段" + fieldName + "初始化失败: " + valueStr, e);
            }
        }
    }

    private static <T> void setListValue(T object, Method method, Class<?>[] parameterTypes, String fieldName, String[] valueList) {
        if (valueList != null && valueList.length != 0) {
            try {
                Class<?> type = parameterTypes[0];
                if (valueList.length > 1) {
                    // TODO 需处理多个list参数问题.
                }
                Object value = ConvertUtils.convertType(valueList[0], type);
                if (value != null) {
                    method.invoke(object, value);
                }
            } catch (Exception e) {
                LOGGER.debug("#字段" + fieldName + "初始化失败: " + JSON.toJSONString(valueList), e);
            }
        }
    }

    /**
     * Reflect bean 2 map map.
     *
     * @param <T>  the type parameter
     * @param bean the bean
     * @return the map
     */
    public static <T> Map<String, Object> reflectBean2Map(T bean) {
        Map<String, Object> result = new HashMap<>();
        try {
            Class<?> clazz   = bean.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getDeclaringClass() != Object.class) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String     methodName     = method.getName();
                    if (parameterTypes.length == 0 && (methodName.startsWith("get") || methodName.startsWith("is"))) {
                        String fieldName = methodName.replaceFirst("^(get|is)", "");
                        fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                        try {
                            Object value = method.invoke(bean);
                            if (value != null) {
                                result.put(fieldName, value);
                            }
                        } catch (Exception e) {
                            LOGGER.debug("#字段" + fieldName + "获取失败", e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.debug("#对象数据获取失败！", e);
        }
        return result;
    }

    /**
     * Reflect bean 2 map map.
     *
     * @param <T>  the type parameter
     * @param bean the bean
     * @return the map
     */
    public static <T> Map<String, Object> reflectBean2MapWithUnderline(T bean) {
        Map<String, Object> result = new HashMap<>();
        try {
            Class<?> clazz   = bean.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getDeclaringClass() != Object.class) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String     methodName     = method.getName();
                    if (parameterTypes.length == 0 && (methodName.startsWith("get") || methodName.startsWith("is"))) {
                        String fieldName = methodName.replaceFirst("^(get|is)", "");
                        fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                        try {
                            Object value = method.invoke(bean);
                            if (value != null) {
                                result.put(NameUtils.toUnderline(fieldName), value);
                            }
                        } catch (Exception e) {
                            LOGGER.debug("#字段" + fieldName + "获取失败", e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.debug("#对象数据获取失败！", e);
        }
        return result;
    }

    public static <T> Map<Object, Object> reflectBean2ObjectMap(T bean) {
        Map<Object, Object> result = new HashMap<>();
        try {
            Class<?> clazz   = bean.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getDeclaringClass() != Object.class) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String     methodName     = method.getName();
                    if (parameterTypes.length == 0 && (methodName.startsWith("get") || methodName.startsWith("is"))) {
                        String fieldName = methodName.replaceFirst("^(get|is)", "");
                        fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                        try {
                            Object value = method.invoke(bean);
                            if (value != null) {
                                result.put(fieldName, value);
                            }
                        } catch (Exception e) {
                            LOGGER.debug("#字段" + fieldName + "获取失败", e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.debug("#对象数据获取失败！", e);
        }
        return result;
    }

    /**
     * Reflect bean 2 map include null value map.
     *
     * @param <T>  the type parameter
     * @param bean the bean
     * @return the map
     */
    public static <T> Map<String, Object> reflectBean2MapIncludeNullValue(T bean) {
        Map<String, Object> result = new HashMap<>();
        try {
            Class<?> clazz   = bean.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getDeclaringClass() != Object.class) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String     methodName     = method.getName();
                    if (parameterTypes.length == 0 && (methodName.startsWith("get") || methodName.startsWith("is"))) {
                        String fieldName = methodName.replaceFirst("^(get|is)", "");
                        fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
                        try {
                            result.put(fieldName, method.invoke(bean));
                        } catch (Exception e) {
                            LOGGER.debug("#字段" + fieldName + "获取失败", e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.debug("#对象数据获取失败！", e);
            return null;
        }
        return result;
    }

    /**
     * Reflect t.
     *
     * @param fields    the fields
     * @param className the class name
     * @return the t
     */
    public static <T> List<T> reflect(List<Map<String, Object>> fields, String className) {
        List<T> results = new ArrayList<>();
        for (Map<String, Object> field : fields) {
            T result = reflect(field, className);
            if (result != null) {
                results.add(result);
            }
        }
        return results;
    }

    /**
     * Reflect t.
     *
     * @param <T>    the type parameter
     * @param fields the fields
     * @param clazz  the clazz
     * @return the t
     */
    public static <T> List<T> reflect(List<Map<String, Object>> fields, Class<T> clazz) {
        List<T> results = new ArrayList<>();
        for (Map<String, Object> field : fields) {
            T result = reflect(field, clazz);
            if (result != null) {
                results.add(result);
            }
        }
        return results;
    }

    /**
     * Reflect with underline list.
     *
     * @param <T>         the type parameter
     * @param fieldsTable the fields table
     * @param className   the class name
     * @return the list
     */
    public static <T> List<T> reflectWithUnderline(List<Map<String, Object>> fieldsTable, String className) {
        List<T> results = new ArrayList<>();
        for (Map<String, Object> fields : fieldsTable) {
            T result = reflectWithUnderline(fields, className);
            if (result != null) {
                results.add(result);
            }
        }
        return results;
    }

    /**
     * Reflect with underline list.
     *
     * @param <T>         the type parameter
     * @param fieldsTable the fields table
     * @param clazz       the clazz
     * @return the list
     */
    public static <T> List<T> reflectWithUnderline(List<Map<String, Object>> fieldsTable, Class<T> clazz) {
        List<T> results = new ArrayList<>();
        for (Map<String, Object> fields : fieldsTable) {
            T result = reflectWithUnderline(fields, clazz);
            if (result != null) {
                results.add(result);
            }
        }
        return results;
    }

    public static <T> List<Map<String, Object>> reflectBean2Map(List<T> list) {
        List<Map<String, Object>> results = new ArrayList<>(list.size());
        for (T bean : list) {
            results.add(reflectBean2Map(bean));
        }
        return results;
    }

    public static <T> List<Map<String, Object>> reflectBean2MapWithUnderline(List<T> list) {
        List<Map<String, Object>> results = new ArrayList<>(list.size());
        for (T bean : list) {
            results.add(reflectBean2MapWithUnderline(bean));
        }
        return results;
    }

    public static <T> List<Map<String, Object>> reflectBean2MapIncludeNullValue(List<T> list) {
        List<Map<String, Object>> results = new ArrayList<>(list.size());
        for (T bean : list) {
            results.add(reflectBean2MapIncludeNullValue(bean));
        }
        return results;
    }

    public static <T> List<T> reflectColumn(List<?> list, String fieldName, Class<T> tClass) {
        if (list == null || list.isEmpty()) {
            return Collections.emptyList();
        }
        List<T> results  = new ArrayList<>(list.size());
        Object  instance = list.get(0);
        Method  method;
        try {
            String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            method = instance.getClass().getMethod(getterName);
        } catch (Exception e) {
            throw new RuntimeException("Class " + instance.getClass().getSimpleName() + " has no field [" + fieldName + "]", e);
        }
        if (method.getReturnType() == tClass) {
            try {
                for (Object k : list) {
                    results.add((T) method.invoke(k));
                }
            } catch (Exception e) {
                throw new RuntimeException("Error Calling method: " + method.getName(), e);
            }
        } else {
            throw new RuntimeException("Field [" + fieldName + "] is not instance of " + tClass.getSimpleName());
        }
        return results;
    }

    public static <T> T field(Object obj, String fieldName) {
        Class<?> tClass = obj.getClass();
        try {
            Field field = tClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            try {
                return (T) field.get(obj);
            } catch (IllegalAccessException e) {
                LOGGER.warn("Cannot access field: " + fieldName, e);
                return null;
            }
        } catch (NoSuchFieldException e) {
            LOGGER.warn("Cannot find field: " + fieldName, e);
            return null;
        }
    }

    public static <T> T callConstructor(Class<T> clazz, Class<?>[] paramTypes, Object... params) throws Throwable {
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes);
            constructor.setAccessible(true);
            return (T) constructor.newInstance(params);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
        return null;
    }

    public static <T> T callStaticMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes, Object... params) throws Throwable {
        try {
            Method method = clazz.getDeclaredMethod(methodName, paramTypes);
            method.setAccessible(true);
            return (T) method.invoke(null, params);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
        return null;
    }

    public static <T> T getStaticField(Class<?> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            return (T) field.get(null);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void setStaticField(Class<?> clazz, String fieldName, Object value) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(null, value);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static <T> T callMethod(Object obj, String methodName, Class<?>[] paramTypes, Object... params) throws Throwable {
        try {
            Method method = obj.getClass().getDeclaredMethod(methodName, paramTypes);
            method.setAccessible(true);
            return (T) method.invoke(obj, params);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
        return null;
    }

    public static <T> T getField(Object obj, String fieldChain) {
        return getField(obj, fieldChain.split("\\."));
    }

    public static <T> T getField(Object obj, String... fieldChain) {
        try {
            for (String fieldName : fieldChain) {
                if (obj == null) {
                    return null;
                }
                Field field = obj.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                obj = field.get(obj);
            }
            return (T) obj;
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void setField(Class<?> clazz, Object obj, String fieldName, Object value) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void setField(Object obj, String fieldName, Object value) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}
